import React, { useState, useEffect, useRef } from "react";
import "./Calendar.scss";

const Calendar = () => {
  const [weekArr] = useState(["日", "一", "二", "三", "四", "五", "六"]);
  const [dataArr, setDataArr] = useState([]);
  const [allDataArr, setAllDataArr] = useState([]);
  const [selectData, setSelectData] = useState({});
  const [isSelectedCurrentDate, setIsSelectedCurrentDate] = useState(false);
  const [translateIndex, setTranslateIndex] = useState(0);
  const [transitionDuration] = useState(0.3);
  const [needAnimation, setNeedAnimation] = useState(true);
  const [isTouching, setIsTouching] = useState(false);
  const [touchStartPositionX, setTouchStartPositionX] = useState(null);
  const [touchStartPositionY, setTouchStartPositionY] = useState(null);
  const [touch, setTouch] = useState({ x: 0, y: 0 });
  const [isWeekView, setIsWeekView] = useState(false);
  const [itemHeight] = useState(50);
  const [needHeightAnimation, setNeedHeightAnimation] = useState(false);
  const [offsetY, setOffsetY] = useState(0);
  const [lineNum, setLineNum] = useState(0);
  const [lastWeek, setLastWeek] = useState([]);
  const [nextWeek, setNextWeek] = useState([]);
  const [isDelay, setIsDelay] = useState(true);
  const [touchAreaHeight] = useState(40);
  const [touchAreaPadding] = useState(10);
  const [isClicked, setIsClicked] = useState(false);
  const calendarRef = useRef(null);

  const getCurrentDate = () => {
    const currentDate = new Date();
    setSelectData({
      year: currentDate.getFullYear(),
      month: currentDate.getMonth() + 1,
      day: currentDate.getDate(),
    });
  };

  const getMonthData = (date, unSelected = false) => {
    const { year, month, day } = date;
    let dataArr = [];
    let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

    if ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0) {
      daysInMonth[1] = 29;
    }

    const monthStartWeekDay = new Date(year, month - 1, 1).getDay();
    const monthEndWeekDay = new Date(year, month, 1).getDay() || 7;

    const preInfo = getPreMonth(date);
    const nextInfo = getNextMonth();

    for (let i = 0; i < monthStartWeekDay; i++) {
      let preObj = {
        type: "pre",
        day: daysInMonth[preInfo.month - 1] - (monthStartWeekDay - i - 1),
        month: preInfo.month,
        year: preInfo.year,
      };
      dataArr.push(preObj);
    }

    for (let i = 0; i < daysInMonth[month - 1]; i++) {
      let itemObj = {
        type: "normal",
        day: i + 1,
        month,
        year,
        isSelected: day === i + 1 && !unSelected,
      };
      dataArr.push(itemObj);
    }

    for (let i = 0; i < 7 - monthEndWeekDay; i++) {
      let nextObj = {
        type: "next",
        day: i + 1,
        month: nextInfo.month,
        year: nextInfo.year,
      };
      dataArr.push(nextObj);
    }

    return dataArr;
  };

  const getPreMonth = (date, appointDay = 1) => {
    let { year, month } = date || selectData;
    if (month === 1) {
      year -= 1;
      month = 12;
    } else {
      month -= 1;
    }

    return { year, month, day: appointDay };
  };

  const getNextMonth = (appointDay = 1) => {
    let { year, month } = selectData;
    if (month === 12) {
      year += 1;
      month = 1;
    } else {
      month += 1;
    }

    return { year, month, day: appointDay };
  };

  const changeAllData = (val) => {
    if (isSelectedCurrentDate && !isWeekView) return;

    const preDate = getPreMonth();
    const preDataArr = getMonthData(preDate, true);
    const nextDate = getNextMonth();
    const nextDataArr = getMonthData(nextDate, true);

    if (isWeekView) {
      const sliceStart = dealWeekViewSliceStart();
      const newPreDataArr = [...preDataArr];
      const newNextDataArr = [...nextDataArr];
      newPreDataArr.splice(sliceStart, 7, ...lastWeek);
      newNextDataArr.splice(sliceStart, 7, ...nextWeek);
      preDataArr = newPreDataArr;
      nextDataArr = newNextDataArr;
    }

    const delayHandle = (isDelay) => {
      setAllDataArr([preDataArr, val, nextDataArr]);
      setNeedAnimation(false);
      setTranslateIndex(0);
      if (isDelay) setIsDelay(false);
    };

    if (isDelay) {
      delayHandle(isDelay);
      return;
    }

    setTimeout(
      () => {
        delayHandle();
      },
      isClicked && isWeekView ? 0 : transitionDuration * 1000
    );
  };

  const checkoutDate = (selectData) => {
    setIsSelectedCurrentDate(true);
    setIsClicked(true);

    if (isWeekView && selectData.type !== "normal") {
      setNeedAnimation(false);
      setNeedHeightAnimation(false);
    }

    if (selectData.type === "next") {
      setTranslateIndex(translateIndex + 1);
      dealMonthData("NEXT_MONTH", selectData.day);
      return;
    }

    if (selectData.type === "pre") {
      setTranslateIndex(translateIndex - 1);
      dealMonthData("PRE_MONTH", selectData.day);
      return;
    }

    setSelectData((prev) => ({ ...prev, day: selectData.day }));
    const oldSelectIndex = dataArr.findIndex(
      (item) => item.isSelected && item.type === "normal"
    );
    const newSelectIndex = dataArr.findIndex(
      (item) => item.day === selectData.day && item.type === "normal"
    );

    if (oldSelectIndex !== -1) {
      const newDataArr = [...dataArr];
      newDataArr[oldSelectIndex].isSelected = false;
      setDataArr(newDataArr);
    }
    if (newSelectIndex !== -1) {
      const newDataArr = [...dataArr];
      newDataArr[newSelectIndex].isSelected = true;
      setDataArr(newDataArr);
    }
  };

  const dealMonthData = (type, appointDay = 1) => {
    setIsSelectedCurrentDate(false);

    switch (type) {
      case "PRE_MONTH":
        setSelectData(getPreMonth("", appointDay));
        break;
      case "NEXT_MONTH":
        setSelectData(getNextMonth(appointDay));
        break;
      default:
        break;
    }

    const newDataArr = getMonthData(selectData);
    setDataArr(newDataArr);
    setLineNum(Math.ceil(newDataArr.length / 7));
  };

  const checkoutCurrentDate = () => {
    setIsDelay(true);
    getCurrentDate();
    dealMonthData();
  };

  const touchStart = (event) => {
    setIsTouching(true);
    setNeedAnimation(true);
    setIsClicked(false);

    setTouchStartPositionX(event.touches[0].clientX);
    setTouchStartPositionY(event.touches[0].clientY);
    setTouch({ x: 0, y: 0 });
  };

  const touchMove = (event) => {
    const moveX = event.touches[0].clientX - touchStartPositionX;
    const moveY = event.touches[0].clientY - touchStartPositionY;

    if (Math.abs(moveX) > Math.abs(moveY)) {
      setNeedHeightAnimation(false);
      setTouch({
        x: moveX / calendarRef.current.offsetWidth,
        y: 0,
      });
    } else {
      setNeedHeightAnimation(true);
      setTouch({
        x: 0,
        y: moveY / calendarRef.current.offsetHeight,
      });
    }
  };

  const touchEnd = () => {
    setIsTouching(false);
    const { x, y } = touch;

    if (Math.abs(x) > Math.abs(y) && Math.abs(x) > 0.3) {
      if (x > 0) {
        setTranslateIndex(translateIndex - 1);
        if (isWeekView) {
          handlePreWeek();
        } else {
          handlePreMonth();
        }
      } else if (x < 0) {
        setTranslateIndex(translateIndex + 1);
        if (isWeekView) {
          handleNextWeek();
        } else {
          handleNextMonth();
        }
      }
    }

    if (
      Math.abs(y) > Math.abs(x) &&
      Math.abs(y * calendarRef.current.offsetHeight) > 50
    ) {
      if (y > 0) {
        setIsWeekView(false);
        setOffsetY(0);
      } else if (y < 0) {
        setIsWeekView(true);
        dealWeekViewData();
      }
    }

    setTouch({ x: 0, y: 0 });
  };

  const getInfoOfWeekView = (selectedIndex, length) => {
    const indexOfLine = Math.ceil((selectedIndex + 1) / 7);
    const totalLine = Math.ceil(length / 7);
    const sliceStart = (indexOfLine - 1) * 7;
    const sliceEnd = sliceStart + 7;

    return { indexOfLine, totalLine, sliceStart, sliceEnd };
  };

  const dealWeekViewSliceStart = () => {
    const selectedIndex = dataArr.findIndex((item) => item.isSelected);
    const { indexOfLine, totalLine, sliceStart, sliceEnd } = getInfoOfWeekView(
      selectedIndex,
      dataArr.length
    );

    setOffsetY(-((indexOfLine - 1) * itemHeight));

    if (indexOfLine === 1) {
      const preInfo = getPreMonth();
      const preDataArr = getMonthData(preInfo, true);
      const preDay =
        dataArr[0].day - 1 || preDataArr[preDataArr.length - 1].day;
      const preIndex = preDataArr.findIndex(
        (item) => item.day === preDay && item.type === "normal"
      );
      const {
        sliceStart: preSliceStart,
        sliceEnd: preSliceEnd,
      } = getInfoOfWeekView(preIndex, preDataArr.length);
      setLastWeek(preDataArr.slice(preSliceStart, preSliceEnd));
    } else {
      setLastWeek(dataArr.slice(sliceStart - 7, sliceEnd - 7));
    }

    if (indexOfLine >= totalLine) {
      const nextInfo = getNextMonth();
      const nextDataArr = getMonthData(nextInfo, true);
      const nextDay =
        dataArr[dataArr.length - 1].type === "normal"
          ? 1
          : dataArr[dataArr.length - 1].day + 1;
      const nextIndex = nextDataArr.findIndex((item) => item.day === nextDay);
      const {
        sliceStart: nextSliceStart,
        sliceEnd: nextSliceEnd,
      } = getInfoOfWeekView(nextIndex, nextDataArr.length);
      setNextWeek(nextDataArr.slice(nextSliceStart, nextSliceEnd));
    } else {
      setNextWeek(dataArr.slice(sliceStart + 7, sliceEnd + 7));
    }

    return sliceStart;
  };

  const handlePreMonth = () => {
    dealMonthData("PRE_MONTH");
  };

  const handleNextMonth = () => {
    dealMonthData("NEXT_MONTH");
  };

  const handlePreWeek = () => {
    dealWeekData("PRE_WEEK");
  };

  const handleNextWeek = () => {
    dealWeekData("NEXT_WEEK");
  };

  const dealWeekData = (type) => {
    const { year, month, day } =
      type === "PRE_WEEK"
        ? lastWeek.find((item) => item.type === "normal")
        : nextWeek[0];
    setSelectData({ year, month, day });
    const newDataArr = getMonthData(selectData);
    setDataArr(newDataArr);
    setLineNum(Math.ceil(newDataArr.length / 7));
    setOffsetY(offsetY - itemHeight);
    dealWeekViewData();
  };

  const dealWeekViewData = () => {
    const sliceStart = dealWeekViewSliceStart();
    const newAllDataArr = [...allDataArr];
    newAllDataArr[0].splice(sliceStart, 7, ...lastWeek);
    newAllDataArr[2].splice(sliceStart, 7, ...nextWeek);
    setAllDataArr(newAllDataArr);
  };

  useEffect(() => {
    checkoutCurrentDate();
  }, []);

  useEffect(() => {
    changeAllData(dataArr);
  }, [dataArr]);

  useEffect(() => {
    if (!isWeekView) {
      setIsSelectedCurrentDate(false);
      changeAllData(dataArr);
    }
  }, [isWeekView]);

  return (
    <div className="calendar">
      <section className="header">
        {selectData.year}年{selectData.month}月{selectData.day}日
      </section>
      <ul className="week-area">
        {weekArr.map((item, index) => (
          <li key={index} className="week-item">
            <span className="week-font calendar-item">{item}</span>
          </li>
        ))}
      </ul>
      <section
        ref={calendarRef}
        className="data-container"
        style={{
          height: isWeekView
            ? `${itemHeight + touchAreaHeight}px`
            : `${lineNum * itemHeight + touchAreaHeight}px`,
          transitionDuration: `${
            needHeightAnimation ? transitionDuration : 0
          }s`,
        }}
        onTouchStart={touchStart}
        onTouchMove={touchMove}
        onTouchEnd={touchEnd}
      >
        <section
          className="month-area"
          style={{
            transform: `translateX(${-(translateIndex + 1) * 100}%)`,
            transitionDuration: `${needAnimation ? transitionDuration : 0}s`,
          }}
        >
          <div
            className="banner-area"
            style={{
              transform: `translateY(${offsetY}px)`,
              transitionDuration: `${
                needHeightAnimation ? transitionDuration : 0
              }s`,
            }}
          >
            {allDataArr.map((monthItem, monthIndex) => (
              <ul
                key={monthIndex}
                className="data-area"
                style={{
                  transform: `translateX(${(translateIndex +
                    (isTouching ? touch.x : 0)) *
                    100}%)`,
                  transitionDuration: `${isTouching ? 0 : transitionDuration}s`,
                }}
              >
                {monthItem.map((item, index) => (
                  <li
                    key={index}
                    className={`data-item ${
                      item.isSelected ? "selected" : ""
                    } ${
                      item.type !== "normal" && !isWeekView ? "other-item" : ""
                    }`}
                    style={{ height: `${itemHeight}px` }}
                    onClick={() => checkoutDate(item)}
                  >
                    <span className="data-font calendar-item">{item.day}</span>
                  </li>
                ))}
              </ul>
            ))}
          </div>
        </section>
        <section
          className="touch-area"
          style={{
            height: `${touchAreaHeight}px`,
            paddingTop: `${touchAreaPadding}px`,
          }}
        >
          <div
            className="touch-container"
            style={{
              height: `${touchAreaHeight - touchAreaPadding}px`,
            }}
          >
            <div className="touch-item"></div>
          </div>
        </section>
      </section>
    </div>
  );
};

export default Calendar;
